package com.qinglei.recoup.longconn.handler;

import com.qinglei.recoup.common.domain.RadarMessage;
import com.qinglei.recoup.longconn.constant.LongConnConstant;
import com.qinglei.recoup.longconn.entity.Message;
import com.qinglei.recoup.longconn.utils.ProtocolUtils;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;

import java.net.SocketAddress;
import java.util.Iterator;
import java.util.Map;

@ChannelHandler.Sharable
@Slf4j
public class HeartBeatHandler extends ChannelInboundHandlerAdapter {

    private DeviceStatusHandler deviceStatusHandler;
    private Map<SocketAddress, Channel> clientChannelMap;
    private Map<String, Channel> deviceChannelMap;


    public HeartBeatHandler() {
    }

    public HeartBeatHandler(Map<SocketAddress, Channel> clientChannelMap, Map<String, Channel> deviceChannelMap,
                            DeviceStatusHandler deviceStatusHandler) {
        this.clientChannelMap = clientChannelMap;
        this.deviceChannelMap = deviceChannelMap;
        this.deviceStatusHandler = deviceStatusHandler;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        Channel channel = ctx.channel();
        Message message = (Message) msg;
        String sn = message.getSn();
        String funcCode = message.getFuncCode();
        initDeviceChannelMap(channel, sn);
        if(funcCode.equals(LongConnConstant.FUNC_HEART_BEAT)){
            Message heart = ProtocolUtils.buildHeartBeatReplyMessage(message);
            log.info(channel.remoteAddress() + ", Get the heartbeat, sn:" + sn);
            channel.writeAndFlush(heart);
        }else {
            ctx.fireChannelRead(msg);
        }
    }

    /**
     * 初始化连接
     * @param channel
     * @param deviceSn
     */
    private void initDeviceChannelMap(Channel channel, String deviceSn) {
        if (deviceChannelMap.get(deviceSn) == null) {
            log.info(channel.remoteAddress() + ", client relevance deviceId : " + deviceSn);
            deviceChannelMap.put(deviceSn, channel);
            deviceStatusHandler.procDeviceStatus(deviceSn, RadarMessage.STATUS_ONLINE);
        }
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        SocketAddress socketAddress = ctx.channel().remoteAddress();
        log.info(socketAddress+ " Channel are Registered" );
        super.channelRegistered(ctx);
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        log.info(ctx.channel().remoteAddress() + " Channel are Unregistered" );
        super.channelUnregistered(ctx);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        SocketAddress socketAddress = ctx.channel().remoteAddress();
        log.info(socketAddress + "  Channel are Activated" );
        clientChannelMap.put(socketAddress, ctx.channel());
        super.channelActive(ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        SocketAddress socketAddress = ctx.channel().remoteAddress();
        log.info(socketAddress + "  Channel are Inactive" );
        removeChannelByMap(socketAddress);
        super.channelInactive(ctx);
    }

    /**
     * 移除过期的连接
     * @param socketAddress
     */
    private void removeChannelByMap(SocketAddress socketAddress) {
        Channel channel = clientChannelMap.remove(socketAddress);
        if (channel != null) {
            Iterator<Map.Entry<String, Channel>> iterator = deviceChannelMap.entrySet().iterator();
            while (iterator.hasNext()){
                Map.Entry<String, Channel> next = iterator.next();
                Channel removeChannel = next.getValue();
                if(channel.equals(removeChannel)){
                    String sn = next.getKey();
                    log.info(socketAddress + ", client disassociate deviceId : " + sn);
                    deviceChannelMap.remove(sn);
                    deviceStatusHandler.procDeviceStatus(sn, RadarMessage.STATUS_OFFLINE);
                }
            }
        }
    }

    /**
     * Calls {@link ChannelHandlerContext#fireUserEventTriggered(Object)} to forward
     * to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
     * <p>
     * Sub-classes may override this method to change behavior.
     *
     * @param ctx
     * @param evt
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            log.info("client idle timeout :{}", ctx.channel().remoteAddress());
            ctx.close();
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error(ctx.channel().remoteAddress() + " Catch exceptions", cause);
    }

}
